/*
 * gutil.c
 * Description: util for GIGI, 
 *              including system porting, socket, string, gigi object,
 * Created: 2009.10.26
 * Version: 1.0
 */

#ifdef HAVE_CONFIG_H
  #include "config.h"
#endif

#include "gbase.h"
#include <malloc.h>
#include <stdarg.h>
#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <time.h>

#ifdef WINDOWS
  #pragma warning (push)
  #pragma warning (disable:6011)
  #include <ws2tcpip.h>
  #pragma warning (pop)
#endif

#define THIS_FILE_ERROR_ID  10000

/* function strings_split
 *
 */
GIGI_LPSTR * strings_split (GIGI_LPCSTR s, int delims)
{
  int nCount, nLen;
  char * p;
  GIGI_LPSTR * r;
  GIGI_LPSTR * ri;

  if ( s == NULL)
  {
    s = "";
  };

  for (nCount = 1, nLen = 0, p = (char * )s; *p ; p++, nLen++ )
  {
    if (delims == *p)
    {
      nCount++;
    }
  }

  p = (char * ) malloc ( sizeof (char * ) * (nCount + 1) + nLen + 3);

  if (p == NULL)
  {
    LOG_OUT_OF_MEMORY();
    return NULL;
  };

  ri = r = (GIGI_LPSTR * )p;
  p += sizeof (char * ) * (nCount + 1);

  strcpy (p, s);

  while (*p)
  {
    while (*p == delims) 
    {
      *(p++) = '\0';
    }

    if (*p == '\0' ) break;

    *(ri++) = p;

    while (*p && *p != delims) 
    {
      p++;
    }
  }

  *ri = '\0';

  return r;
};

/* function strings_join
 *
 */
char * strings_join (char * buf, int buf_size, GIGI_LPCSTR * argv,
                     int argc, char delims, char ch)
{
  char buf_static[256];
  int * argvLen, i, totalSize;
  char * p;

  /* Step 1 : calc the count of argv */
  if (argc < 0 )
  {
    argc = strings_count (argv);
  };

  /* Step 2 : calc the memory size of output */
  argvLen = STATIC_ALLOC (int * , buf_static, sizeof (int) * argc );

  if (argvLen == NULL)
  {
    LOG_OUT_OF_MEMORY();
    return NULL;
  }

  for (i = 0, totalSize = 0; i < argc ; i++)
  {
    argvLen [i] = (int)strlen (argv[i]);
    totalSize += argvLen[i];
    totalSize += 1;
    
    if (buf_size >0 && totalSize > buf_size)
    {
      argc = i;
      break;
    }
  }

  /* step 3 : alloc and check the buf */
  if (buf == NULL)
  {
    buf_size = totalSize;

    buf = (char * ) malloc (buf_size);

    if (buf == NULL)
    {
      LOG_OUT_OF_MEMORY();
      goto Cleanup;
    }
  }

  /* Step 4 : join */
  for (i = 0, p = buf; i < argc; i++)
  {
    int nExt = ch ? strchr (argv[i], delims) != NULL : GIGI_FALSE;

    if ( i )
    {
      *(p++) = delims;
    };

    if (nExt)
    {
      *(p++) = ch;
    };

    if ( argvLen[i] > 0 )
    {
      memcpy (p, argv[i], argvLen[i]);
      p += argvLen[i];
    }

    if (nExt)
    {
      *(p++) = ch;
    }
  }

  *p = 0;

Cleanup:;
  
  STATIC_FREE (argvLen, buf_static);

  return buf;
};

/* function strings_count
 *
 */
int strings_count(GIGI_LPCSTR * s)
{
  int n = 0;

  if (s)
  for (; *s; s++,n++) {};

  return n;
};


/* function string_parser_as_ini_line
 */
char* string_parser_as_ini_line (char * line, int * type)
{
  char *key, *value;
  int tmp;

  if (type == NULL)
  {
    type = &tmp;
  }

  /* trim left */
  
  while (*line == ' ')
  {
    line++;
  };

  /* trim left for section */
  
  while (*line == '[' && line[1] ==' ')
  {
    line++;
    *line = '[';
  };

  /* skip for CPP style comment */

  if ( (line[0] == '//' && line[1] =='//') ||
       (line[0] == '-'  && line[1] =='-' ) )
  {
    line[0] = '\0';
    *type = 1;

    return line;
  }

  /* parser section part */
  if (line[0] == '[')
  {
    key = strchr (line + 1, ']');

    /* error check  
     *   error case 1: [asdfasd
     *   error case 2: []
     */
    if ( key == NULL || key == (line + 1) )
    {
      line[0] = '\0';
      *type = 0;

      return line;
    }

    /* trim right */
    *(key--) = 0;

    while ( *key == ' ')
    {
      *(key--) = '\0';
    }

    *type = 2;

    return line;
  };

  key = strchr (line, '=');

  if ( key == NULL || key == line)
  {
    line[0] = '\0';
    *type = 0;

    return line;
  }

  for (value = key + 1; *value == ' '; value++)
  {
  };

  /* trim right */
  *(key--) = 0;

  while ( *key == ' ')
  {
    *(key--) = '\0';
  }

  key += 2;

  if ( key != value)
  {
    while (*value)
    {
      *(key++) = *(value++);
    }

    *key = 0;
  }
  else
  {
    key = value + strlen (value);
  };

  for (key--; *key == ' ';)
  {
    *(key--) = 0;
  }

  *type = 3;

  return line;
};

/* function conn_string_parser
 *
 */
GIGI_LPSTR * conn_string_parser ( GIGI_LPCSTR conn_string, int delims)
{
  GIGI_LPSTR * ri;
  GIGI_LPSTR * r;

  r = strings_split (conn_string, delims);

  if ( NULL == r)
  {
    return NULL;
  }

  for (ri = r; *ri; ri++)
  {
    *ri = string_parser_as_ini_line (*ri, NULL);
  }

  return r;
};

unsigned string_hash (unsigned n, const void * p, unsigned len)
{
  unsigned char *pE,*pB;

  for ( pB = (unsigned char * )p, pE = pB + len; pB < pE; pB++)
  {
    n =  (n << 5) + (n >> 27);
    n +=  *pB;
    n ++;
  }

  return n;
};

GIGI_LPCSTR conn_string_get (GIGI_LPSTR * conn, GIGI_LPCSTR key,
                             GIGI_LPCSTR pDef)
{
  for (;*conn;conn++)
  {
    if (strcmp (*conn, key) == 0)
    {
      return *conn + strlen (*conn) + 1;
    }
  }

  return pDef;
}

int conn_string_get_list (GIGI_LPSTR * conn, GIGI_LPCSTR key,
                          GIGI_LPCSTR * list,int count)
{
  int n = 0;
  for (;*conn && n < count;conn++)
  {
    if (strcmp (*conn, key) == 0)
    {
      list[n++] = *conn + strlen (*conn) + 1;
    }
  }
  return n;
}

unsigned conn_string_get_ip (GIGI_LPSTR * conn, GIGI_LPCSTR key,
                             GIGI_LPCSTR def)
{
  GIGI_LPCSTR sIP;
  unsigned nIP;

  sIP = conn_string_get (conn, key, def);
  nIP = (sIP == NULL || *sIP == 0 ) ? INADDR_NONE : inet_addr (sIP );

  return nIP;
};

int conn_string_get_int (GIGI_LPSTR * conn, GIGI_LPCSTR key,
                         int def, int nMin, int nMax)
{
  GIGI_LPCSTR p;
  int n;

  p = conn_string_get (conn, key, NULL);
  n = p ? atoi (p) : def;
  
  if ( n < nMin ) 
    n = nMin;
  else if ( n > nMax ) 
    n = nMax;
  
  return n;
};

char * node_id_to_string (GIGI_ID id, char * s)
{
  unsigned uIP;
  unsigned char * sIP;

  uIP = GIGI_ID_IP(id);
  sIP = (unsigned char * )&uIP;

  sprintf (s, "%u.%u.%u.%u(%u,%x)",
      sIP[3], sIP[2], sIP[1], sIP[0], 
      GIGI_ID_PORT(id), 
      GIGI_ID_FLAG(id)
  );

  return s;
}

char * service_id_to_string (GIGI_ID id, char * s)
{
  unsigned uIP = GIGI_ID_IP(id);
  unsigned short uPORT = GIGI_ID_PORT(id);
  unsigned short uFLAG = GIGI_ID_FLAG(id);
  unsigned char * sIP = (unsigned char * )( &uIP );

  if ( uIP == 0 &&
     uPORT == 0 )
  {
    sprintf (s, "G(%u)", uFLAG );
  }
  else
  {
    sprintf (s, "P(%u.%u.%u.%u:%u,%u)", 
      sIP[3], sIP[2], sIP[1], sIP[0],
      uPORT,
      uFLAG
    );
  }

  return s;
};

static char * gigi_process_name (char * name, int size)
{
  char filename[GIGI_MAX_PATH],*pName,*pExt;
  int name_size;
  
#ifdef WINDOWS  
  name_size = GetModuleFileNameA (NULL, filename, sizeof (filename) - 1);
  filename[name_size] = 0;
  pName = (char * )strrchr (filename, '\\');
#else
  name_size = readlink("/proc/self/exe", filename, sizeof(filename) - 1 );
  name_size = name_size < 0 ? 0 : name_size;
  filename[name_size] = 0;
  pName = (char * )strrchr(filename,'/');
#endif

  pExt  = pName ? strchr(pName + 1,'.') : NULL;
  if (pExt) 
  {
    *pExt = 0;
  }

  strncpy (name, pName ? pName + 1 : "", size - 1);

  name [ size - 1] = '\0';

  return name;  
}

// format 
// type|log-count|time|process name|tid|file|line|func name
// 
void gigi_log (GIGI_LPCSTR file, int nLine, GIGI_LPCSTR pFunc,
               GIGI_LPCSTR pType, GIGI_LPCSTR pFmt, ...)
{
  static char ProcessName[128] = {0};
  enum {log_header_size = 256};
  char buf_static [1024], *log_header, *pBuf = NULL;
  GIGI_LPCSTR p;
  int nL, n = 0, nCount, nTime;

  // make log string
  //
  for ( nL = sizeof (buf_static); nL > 0 ; nL <<= 1 )
  {
    va_list va;
    int nBufLen = nL - log_header_size;
    
    va_start (va, pFmt);
    pBuf = STATIC_ALLOC (char *, buf_static, nL );
  
    if (pBuf == NULL)
    {
      return;
    }

    log_header = pBuf + nBufLen;

    n = vsnprintf (pBuf, nBufLen - 4, pFmt, va);

    if ( n < (nBufLen - 8) )
    {
      break;
    }

    STATIC_FREE (pBuf, buf_static);
  }

  strcat (pBuf + n, "\n");

  for (nCount = 0, p = pBuf; *p ; p++ )
  {
    if ( *p == '\n') nCount++;
  }
  
  if (ProcessName[0] == 0)
  {
    gigi_process_name (ProcessName, sizeof (ProcessName) - 16);
    sprintf (ProcessName + strlen(ProcessName), "(%u)", PROCESS_ID());
  };

  // adjust file name
  //
  for ( p = file + strlen (file) - 1, n = 0; p > file; p--)
  {
    if ( *p != '\\' && *p != '/' )
    {
      continue;
    }

    if ( (++n)  ==  2 )
    {
      file = p + 1;
      break;
    }
  }

  // type, count, time, process name, tid, filename,fileline, funcname
  nTime = ( (int)time(0) % 86400 );

  sprintf (    
    log_header,
    "|%d|%s|%u:%u:%u|%s|%u|%s|%u|%s|\r\n", 
    nCount,
    pType  , 
    nTime / 3600 , (nTime % 3600) / 60 , nTime % 60 ,
    ProcessName ,
    THREAD_ID(),
    file,
    nLine,
    pFunc
  );

  fputs ( log_header, stderr);
  fputs ( pBuf      , stderr);

  STATIC_FREE (pBuf,buf_static);
}

GIGI_TIME gigi_now ()
{

  GIGI_TIME tm;

#ifdef WINDOWS
  GetSystemTimeAsFileTime ( (LPFILETIME)&tm);
#else
  struct timeval tv;
  gettimeofday (&tv, NULL);
  tm = tv.tv_sec;
  tm *= 10000000;
  tm += (tv.tv_usec * 10 );
#endif
  return tm;
}

int gigi_calc_tps (GIGI_TIME tm, int nCount)
{
  tm = gigi_now () - tm;
  tm /= nCount;
  return tm == 0 ? 1 : 10000000 / (int)tm;
};

/**
* @brief associate a local address with a socket
* @param nPort port number
* @return FD_SOCKET
*/
FD_SOCKET fd_tcp_bind (int * port)
{
  struct sockaddr_in addr;
  int addrlen;
  int bool_value=1;
  FD_SOCKET fd;
  
  // step 1: Create socket
  //
  fd = socket (AF_INET, SOCK_STREAM, 0);
  
  if (FD_SOCKET_VALID ( fd ) == GIGI_FALSE)
  {
    return fd;
  }
  
  // step 2: Bind and Listen
  //
  memset (&addr, 0, sizeof (addr));
  addr.sin_family = AF_INET;
  addr.sin_port = htons ( (short) (*port));
  addr.sin_addr.s_addr = htonl ( INADDR_ANY);

  if ( 0 != bind ( fd, (struct sockaddr* )&addr, sizeof (addr)) || 
     0 != listen (fd, 5) ) 
  {
    FD_CLOSE_SOCKET (fd);
    return fd;
  }

  // step 3: set socket option
  //
  /* ask for NODELAY, this one is rather important */
  setsockopt (fd, IPPROTO_TCP, TCP_NODELAY,
              (GIGI_LPCSTR) &bool_value, sizeof (int));

  // step 4: if bind to anyport, then return the binded port number
  //
  if ( *port == 0 )
  {
    addrlen = sizeof (addr);
    getsockname (fd, (struct sockaddr* )&addr, (socklen_t* )&addrlen);
    *port = ntohs ( addr.sin_port );
  }

  return fd;
};

static int fd_setblockmode ( FD_SOCKET fd, int isBlock)
{
#ifdef WINDOWS
  unsigned long flag = isBlock ? 0 : 1;
  return ioctlsocket (fd, FIONBIO, &flag);
#else
  int flags = fcntl (fd, F_GETFL, 0);
  if (isBlock) 
    flags &= ~O_NONBLOCK;
  else
    flags |= O_NONBLOCK;
  return fcntl (fd, F_SETFL, flags);
#endif
}


/**
* @brief associate a local address with a socket
* @param nTP   IP address in number  
* @param nPort port number
* @return FD_SOCKET
*/
FD_SOCKET fd_connect (unsigned int nIP, int nPort, int timeout)
{
  struct sockaddr_in addr;
  int bool_value = 1;
  int n;
  struct timeval tv;
  fd_set cnn,econn;
  FD_SOCKET fd = socket (AF_INET, SOCK_STREAM, 0);

  if (FD_SOCKET_VALID (fd) == GIGI_FALSE)
  {
    return fd;
  }

  memset (&addr, 0, sizeof (addr));
  addr.sin_family = AF_INET;
  addr.sin_port   = htons ( (short)nPort );
  addr.sin_addr.s_addr = htonl ( nIP );

  if ( timeout < 0 )
  {
    if ( 0 != connect (fd, (const struct sockaddr* )&addr, sizeof (addr)) )
    {
      FD_CLOSE_SOCKET (fd);
      return fd;
    }
  }
  else if (fd_setblockmode (fd, 0) != 0 )
  {
    FD_CLOSE_SOCKET (fd);
    return fd;
  }
  else
  {
#ifdef WINDOWS
    int nErr;
#else
    retry:
#endif
    n= connect (fd, (const struct sockaddr* )&addr, sizeof (addr));

#ifdef WINDOWS
    nErr = WSAGetLastError();
    if ( n < 0 && nErr != WSAEWOULDBLOCK )
#else
    if (n < 0 && errno == EINTR)
        goto retry;
    if ( n < 0 && errno != EINPROGRESS )
#endif
    {
      FD_CLOSE_SOCKET (fd);
      return fd;
    }

    tv.tv_sec = timeout / 1000;
    tv.tv_usec= timeout % 1000;
    tv.tv_usec*= 1000;

    FD_ZERO (&cnn);
    FD_SET (fd, &cnn);        
    FD_ZERO (&econn);
    FD_SET (fd, &econn);

#ifdef WINDOWS
    n = select (0, NULL, &cnn, &econn, &tv);
#else
    n = select (fd + 1, NULL, &cnn, &econn, &tv);
    if (n > 0)
    {
      int nErrNo,slen = sizeof (nErrNo);
      getsockopt(fd, SOL_SOCKET, SO_ERROR, (void * )&nErrNo, &slen);
      if (nErrNo != 0)
      {
        n = 0;
      }
    }
#endif

    if ( n != 1 || fd_setblockmode (fd, 1) != 0 || FD_ISSET(fd,&econn) )
    {
      FD_CLOSE_SOCKET (fd);
      return fd;
    }  
  }

  /* ask for NODELAY, this one is rather important */
  setsockopt (fd, IPPROTO_TCP, TCP_NODELAY,
              (GIGI_LPCSTR) &bool_value, sizeof (int));

  return fd;
};

int fd_recv (FD_SOCKET fd, void * buff, int size, int nTimeout)
{
  struct timeval tv;
  fd_set rdset, eset;
  int nReaded = 0;
  int n = GIGI_OK;

#ifdef WINDOWS
  int nErrCount = 10;
#endif
  int nErr = GIGI_E_READ_ERROR;
  
  assert( size > 0 );

  if ( size <= 0 )
  {
    return nErr;
  }

  //GIGI_PERF_BEGIN

  if (buff == 0)
  {
    static char sBuf[2048];
    for ( nReaded = size; nReaded > 0; )
    {
      n = nReaded < (int)sizeof (sBuf) ? nReaded : (int)sizeof (sBuf);
      n = fd_recv (fd, sBuf, n, nTimeout);
      
      if ( n <= 0 )
      {
        return n < 0 ? n : size - nReaded;
      }
      nReaded -= n;
    }
    return 0; 
  };

  while ( nReaded < size )
  {
    if (nTimeout >= 0)
    {
      tv.tv_sec = nTimeout / 1000;
      tv.tv_usec = (nTimeout % 1000) * 1000;

      FD_ZERO (&rdset);
      FD_SET (fd, &rdset);

      FD_ZERO (&eset);
      FD_SET (fd, &eset);

#ifdef WINDOWS
      n = select (0, &rdset, NULL, &eset, &tv);

      if (n < 0 && nErrCount >0 && WSAGetLastError () == WSAEINTR)
      {
        nErrCount--;
        
        Sleep (10);

        GIGI_ERROR ("recv a error %d", n );
          
        continue;
      }
#else
      n = select (fd + 1, &rdset, NULL, &eset, &tv);

      if (n > 0)
      {
        int nErrNo,slen = sizeof (nErrNo);
        getsockopt(fd, SOL_SOCKET, SO_ERROR, (void * )&nErrNo, &slen);
        if (nErrNo != 0)
        {
          return GIGI_E_CLOSED_BY_PEER;
        }
      }

#endif

      if ( n < 0 || FD_ISSET (fd, &eset) )
      {
        return GIGI_E_INTERNAL_ERROR;
      } 
      else if ( n == 0 )
      {
        return GIGI_E_TIMEOUT;
      }

      assert ( FD_ISSET (fd, &rdset) );
    }

    n = recv (fd, ( (char * )buff) + nReaded, size - nReaded, 0);

#ifdef WINDOWS
    if ( n < 0 )
    {
      int WSAErr = WSAGetLastError();

      GIGI_ERROR ("Socket Error %d at recv", WSAErr );
      
      if ( WSAErr == WSAECONNRESET || WSAErr == WSAECONNABORTED)
      {
        nErr = GIGI_E_CLOSED_BY_PEER;
      }
      else if (nErrCount > 0 && WSAErr == WSAEINTR )
      {
        nErrCount--;

        Sleep (50);
        
        continue;
      }
    }
#endif

    if (n <= 0)
    {
      return n == 0 ? GIGI_E_CLOSED_BY_PEER : nErr;
    }

    nReaded += n;
  }

  //GIGI_PERF_CHECK (50, fd_read)
  return size;
};

/**
 * @brief read data from a established socket
 * @param fd   established socket
 * @param buff buffer for incoming data
 * @param size length in bytes of buff
 * @param nTimeout seconds for time out
 * @return return number of bytes received
 */
int fd_send (FD_SOCKET fd, const void * buff, int size, int nTimeout)
{
  int nSented    = 0;
  struct timeval tv;
  fd_set rdset;
  fd_set eset;

  int n;
#ifdef WINDOWS
  int nErrCount = 10;
#endif
  GIGI_PERF_BEGIN

  while ( nSented < size )
  {
    if (nTimeout >= 0 )
    {
      tv.tv_sec = nTimeout / 1000;
      tv.tv_usec = (nTimeout % 1000) * 1000;

      FD_ZERO (&rdset);
      FD_SET (fd, &rdset);

      FD_ZERO (&eset);
      FD_SET (fd, &eset);

#ifdef WINDOWS
      n = select (0, NULL, &rdset, &eset, &tv);

      if ( n <0 && nErrCount > 0 && WSAGetLastError () == WSAEINTR )
      {
        nErrCount--;

        Sleep (10);

        GIGI_ERROR ("write recv a error %d", n);

        continue;
      }
#else
      n = select (fd + 1, NULL, &rdset, &eset, &tv);

    if (n > 0)
      {
        int nErrNo,slen = sizeof (nErrNo);
        getsockopt(fd, SOL_SOCKET, SO_ERROR, (void * )&nErrNo, &slen);
        if (nErrNo != 0)
        {
          return GIGI_E_CLOSED_BY_PEER;
        }
      }

#endif
      if (n <0 || FD_ISSET (fd, &eset) )
      {
        return GIGI_E_INTERNAL_ERROR;
      }
      else if (n == 0)
      {
        return GIGI_E_TIMEOUT;
      };

      assert ( FD_ISSET (fd, &rdset) );
    };

    n = send (fd, ( (GIGI_LPCSTR)buff) + nSented, size - nSented, 0);
      
#ifdef WINDOWS
    if ( n <= 0 )
    {
      int nWSAErr = WSAGetLastError ();

      if (n < 0 
          && nErrCount > 0 
          && (nWSAErr == WSAEINTR || nWSAErr == WSAECONNABORTED) )
      {
        nErrCount--;

        Sleep (10);
      
        continue;
      };

      if (n < 0 ) 
      {
        n = -nWSAErr;
      }
    }
#endif
    if ( n == 0 )
    {
      GIGI_ERROR ("Fatal Error at socket send", 0);
      n = GIGI_E_INTERNAL_ERROR;
    }

    if (n <0 )
    {
      return n;
    }

    nSented += n;
  }

  GIGI_PERF_CHECK (50)

  return nSented;
};

unsigned fd_getip (GIGI_LPCSTR s, int * port, int * isUniformAddr)
{
  char sHostName[128];
  struct hostent* pH;
  int nLen = (int)strlen(s);
  unsigned i, nIP, nIPCount;

  if (nLen >= sizeof (sHostName))
  {
    return 0;
  }

  memcpy (sHostName, s, nLen + 1);

  // parse port
  //
  do {
    char * sPORT = strchr (sHostName, ':');
    int nGetPORT = 0;

    if (sPORT == NULL)
    {
      break;
    }

    
    (*sPORT++) = '\0';

    nGetPORT =  atoi (sPORT);
    
    if (nGetPORT >0 && port)
    {
      *port = nGetPORT;
    }    
  } while (0);

  // get ip
  //
  pH = gethostbyname (sHostName);

  if (pH == NULL) 
  {
    return 0;
  }

  for (i=0, nIP = 0, nIPCount = 0; i < (unsigned) (pH->h_length); i += 4 )
  {
    unsigned n = ntohl ( *(unsigned* ) (pH->h_addr_list[i]) );
    
    if (n == INADDR_LOOPBACK) 
    {
      continue;
    }

    nIPCount ++;
    
    if (n > nIP) 
    {
      nIP = n;
    }
  }

  if (isUniformAddr)
  {
    *isUniformAddr = nIPCount <= 1;
  }

  return nIP ? nIP : INADDR_LOOPBACK;
};

unsigned fd_gethostid (int * isAddr)
{
  char sHostName[64];

  return gethostname (sHostName, sizeof(sHostName)) == 0 ? fd_getip (sHostName, NULL, isAddr) : 0;
};

int fd_get_port (FD_SOCKET fd)
{
  int rc;
  struct sockaddr_in name;
  int namelen = sizeof (name);
  rc = getsockname (fd, (struct sockaddr * )&name, (socklen_t* )&namelen);
  if (rc < 0)
  {
    return GIGI_E_SOCKET_ERROR;
  }
  return ntohs (name.sin_port);
}

int fd_socket_pipe (FD_SOCKET helpFD, FD_SOCKET * in, FD_SOCKET * out)
{
  struct sockaddr_in addr;
  int addrlen = sizeof (addr);
  int iErr = 0;
  int tmpHelpFD = 0;
  int bool_value=1;

  *in = INVALID_SOCKET;
  *out= INVALID_SOCKET;

  if (helpFD == INVALID_SOCKET)
  {
    int nPORT = 0;

    helpFD = fd_tcp_bind (&nPORT);

    if (helpFD == INVALID_SOCKET)
    {
      return -1;
    }

    tmpHelpFD = 1;
  }

  memset (&addr, 0, sizeof (addr));
  addr.sin_family = AF_INET;

  iErr = getsockname (helpFD, (struct sockaddr* )&addr,
                      (socklen_t* )&addrlen);
  
  if ( iErr != 0 ) 
  {
    goto Cleanup;
  };

  *in = socket (AF_INET, SOCK_STREAM, 0);
  
  if ( FD_SOCKET_VALID (*in) == GIGI_FALSE ) 
  {
    iErr = GIGI_E_INTERNAL_ERROR;
    goto Cleanup;
  }

  iErr = fd_setblockmode (*in, 0);
  if ( iErr != 0 )
  {
    goto Cleanup;
  };

  addr.sin_addr.s_addr = htonl ( INADDR_LOOPBACK);

  connect (*in, (struct sockaddr* )&addr, sizeof (addr));

  addrlen = sizeof (addr);
  *out = accept (helpFD, (struct sockaddr* )&addr, (socklen_t* )&addrlen);
  
  if ( FD_SOCKET_VALID (*out) == GIGI_FALSE)
  {
    iErr = GIGI_E_INTERNAL_ERROR;
    goto Cleanup;
  }

  iErr = fd_setblockmode (*in, 1);
  if (iErr != 0 )
  {
    goto Cleanup;
  }

  iErr = setsockopt (*in, IPPROTO_TCP, TCP_NODELAY, 
                    (GIGI_LPCSTR) &bool_value, sizeof (int));
  if (iErr != 0)
  {
    goto Cleanup;
  }

  iErr = setsockopt (*out, IPPROTO_TCP, TCP_NODELAY,
                    (GIGI_LPCSTR) &bool_value, sizeof (int));
  if (iErr != 0)
  {
    goto Cleanup;
  }

Cleanup:

  if ( iErr != 0 )
  {
    FD_CLOSE_SOCKET ( *in );
    FD_CLOSE_SOCKET ( *out);    
  }

  if ( tmpHelpFD )
  {
    FD_CLOSE_SOCKET (helpFD);
  }

  return iErr;
};


// File Stream and Socket Stream 
//
//
static int gigi_file_read (GIGI_STREAM s, void * pData, int size);
static int gigi_file_write(GIGI_STREAM s, const void * pData, int size);
static int gigi_file_close(GIGI_STREAM s, int n);
static int gigi_file_cntl (GIGI_STREAM s, int cmd, ...) { return 0; };

struct gigi_stream_vt g_file_stream_vt = 
{
  gigi_file_read, 
  gigi_file_write, 
  gigi_file_close,
  gigi_file_cntl
};

#if defined(SOLARIS)
FILE * fopen_solaris (GIGI_LPCSTR filename, GIGI_LPCSTR type)
  {
    size_t r = 0;
    char buf[1024];

    extern size_t confstr (int, char *, size_t);
    r = confstr (_CS_LFS_CFLAGS, buf, 1024);

    if (r > 0)
     return fopen64 (filename, type);
    else
    return fopen (filename, type);
  };
#endif

static int gigi_file_read (GIGI_STREAM s, void * pData, int size)
{
  struct gigi_stream_file_t * file = (struct gigi_stream_file_t * )(s);
  int nReaded;

  if ( s == NULL)
  {
    return 0;
  }

#ifdef WINDOWS
  if ( ! ReadFile (file->fp, pData, size, (DWORD* )&nReaded, NULL) )
  {
    nReaded = 0;
  }
#else
  nReaded = fread (pData, 1, size, file->fp);
#endif

  return nReaded <= 0 ? 0 : nReaded;
};

static int gigi_file_write(GIGI_STREAM s, const void * pData, int size)
{
  struct gigi_stream_file_t * file = (struct gigi_stream_file_t * )(s);
  int nSended;

  if ( s == NULL)
  {
    return 0;
  }

#ifdef WINDOWS
  if ( ! WriteFile (file->fp, pData, size, (DWORD* )&nSended, NULL ) )
  {
    nSended = 0;
  }
#else
  nSended = fwrite (pData, 1, size, file->fp);
#endif

  return nSended <=0 ? 0 : nSended;
};

static int gigi_file_close  (GIGI_STREAM s, int n)
{
  struct gigi_stream_file_t * file = (struct gigi_stream_file_t * )(s);

  if (s == NULL)
  {
    return GIGI_E_INTERNAL_ERROR;
  }

#ifdef WINDOWS
  WIN_CLOSE_FILE_HANDLE (file->fp);
#else
  if ( file->fp )
  {
    fclose (file->fp);
    file->fp = NULL;
  }
#endif
  
  free ( file);

  return GIGI_OK;
}

GIGI_STREAM gigi_fopen (GIGI_LPCSTR filename_p, GIGI_LPCSTR type)
{
  struct gigi_stream_file_t * file = NEW_OBJECT(struct gigi_stream_file_t);

  if (file == NULL)
  {
    LOG_OUT_OF_MEMORY ();
    return NULL;
  };

#ifdef WINDOWS
  file->fp = CreateFileA(
    filename_p, 
    *type == 'r' ? GENERIC_READ : GENERIC_WRITE, 
    *type == 'r' ? FILE_SHARE_READ | FILE_SHARE_WRITE : 0, 
    NULL, 
    *type == 'r' ? OPEN_EXISTING : CREATE_ALWAYS, 
    0, 
    NULL
  );

  if (file->fp == INVALID_HANDLE_VALUE)
  {
    free (file);
    return NULL;
  }
#else
  file->fp = g_fopen (filename_p, type);

  if (file->fp == NULL)
  {
    free (file);
    return NULL;
  }
#endif

  file->vt = &g_file_stream_vt;
  return (GIGI_STREAM)file;
};

static int socket_stream_read (GIGI_STREAM s, void * pData, int size);
static int socket_stream_write(GIGI_STREAM s, const void * pData, int size);
static int socket_stream_close(GIGI_STREAM s, int n);
static int socket_stream_cntl (GIGI_STREAM s, int cmd, ...);

struct gigi_stream_vt g_socket_stream_vt = 
{
  socket_stream_read,
  socket_stream_write,
  socket_stream_close,
  socket_stream_cntl
};

// 
//
static int socket_stream_read  (GIGI_STREAM s, void * buff, int size)
{
  struct fd_stream_t * fs = (struct fd_stream_t * )s;

  return fs ? fd_recv (fs->_fd, buff, size, fs->_timeout) 
            : GIGI_E_INTERNAL_ERROR;
};

static int socket_stream_write (GIGI_STREAM s, const void * buff, int size)
{
  struct fd_stream_t * fs = (struct fd_stream_t * )s;

  return fs ? fd_send (fs->_fd, buff, size, fs->_timeout) 
            : GIGI_E_INTERNAL_ERROR;
}

static int socket_stream_cntl  (GIGI_STREAM s,int cmd,...)
{
  int n;
  struct fd_stream_t * fs = (struct fd_stream_t * )s;

  va_list va;
  va_start (va, cmd);

  if (cmd == GIGI_FIO_GET_BO )
  {
    int * r = va_arg (va,int * );
    *r = fs->_bo;
    n = 0;
  }
  else
  {
    n = -1;
  }
  return n;
};

static int socket_stream_close (GIGI_STREAM s, int n)
{
  struct fd_stream_t * fs = (struct fd_stream_t * )s;

  if (fs)
  {
    if ( FD_SOCKET_VALID (fs->_fd) )
    {
      shutdown (fs->_fd, 2);
      closesocket (fs->_fd);
    }

    free (fs);
  }

  return GIGI_OK;
}

GIGI_STREAM fd_open (FD_SOCKET fd)
{
  struct fd_stream_t * s = NEW_OBJECT (struct fd_stream_t);

  if (s == NULL)
  {
    LOG_OUT_OF_MEMORY();
  }
  else
  {
    fd_stream_init( *s );
    s->_fd = fd;
  }
  return (GIGI_STREAM)s;
};


